/*
 * Sandora, a Falling Sand Game for the Pandora
 * Copyright (C) 2011  WaveHack <projects.sandora@wavehack.net>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
#include <SDL/SDL.h>
#include <math.h>
#include "common.h"
#include "particle.h"

#define PARTICLECANVAS_WIDTH     (SCREEN_WIDTH / PARTICLE_ZOOM)
#define PARTICLECANVAS_HEIGHT    (SCREEN_HEIGHT / PARTICLE_ZOOM)

#define ONEIN(i)                 ((rand()%(i))==0)
#define SQR(x)                   ((x)*(x))

#define PARTICLE(x,y)            particleScreen[((x)+((y)*PARTICLECANVAS_WIDTH))%(PARTICLECANVAS_WIDTH*PARTICLECANVAS_HEIGHT)]
#define PARTICLE_ABOVE(x,y)      PARTICLE(x,y-1)
#define PARTICLE_BELOW(x,y)      PARTICLE(x,y+1)
#define PARTICLE_LEFT(x,y)       PARTICLE(x-1,y)
#define PARTICLE_RIGHT(x,y)      PARTICLE(x+1,y)
#define PARTICLE_ABOVETWO(x,y)   PARTICLE(x,y-2)

#define PARTICLE_WATER_SPREAD    4

#define PT_STILLBORN_LOWER_BOUND 1
#define PT_STILLBORN_UPPER_BOUND 13
#define PT_FLOATING_LOWER_BOUND  15
#define PT_FLOATING_UPPER_BOUND  18

// Global variables
particleType_t currentParticleType = PT_SAND;

// Static variables
static particleType_t *particleScreen;
static Uint32 particleColors[MAX_PARTICLE_TYPES];

// Static prototypes
static inline bool isStillborn(particleType_t pt);
static inline bool isFloating(particleType_t pt);
static inline bool isBurnable(particleType_t pt);
static inline bool burnsAsEmber(particleType_t pt);
static inline bool isMovedParticle(particleType_t pt);
static inline void drawParticle16(int x, int y, particleType_t pt);
static void doParticleLogic(int x, int y);

// Global functions
/**
 * Allocates the needed memory to the particle canvas and initializes particle
 * colors. This function must be called before trying to draw particles!
 */
void initParticles(void) {
	particleScreen = (particleType_t *)malloc(sizeof(particleType_t) * (PARTICLECANVAS_WIDTH * PARTICLECANVAS_HEIGHT));

	// Stillborn
	particleColors[PT_WALL]     = SDL_MapRGB(screen->format, 100, 100, 100);
	particleColors[PT_IRONWALL] = SDL_MapRGB(screen->format, 110, 110, 110);
	particleColors[PT_TORCH]    = SDL_MapRGB(screen->format, 139,  69,  19);
	particleColors[PT_STOVE]    = SDL_MapRGB(screen->format,  74,  74,  74);
	particleColors[PT_ICE]      = SDL_MapRGB(screen->format, 175, 238, 238);
	particleColors[PT_RUST]     = SDL_MapRGB(screen->format, 110,  40,  10);
	particleColors[PT_EMBER]    = SDL_MapRGB(screen->format, 127,  25,  25);
	particleColors[PT_PLANT]    = SDL_MapRGB(screen->format,   0, 150,   0);
	particleColors[PT_VOID]     = SDL_MapRGB(screen->format,  60,  60,  60);

	// Spouts
	particleColors[PT_WATERSPOUT] = SDL_MapRGB(screen->format,   0,   0, 128);
	particleColors[PT_SANDSPOUT]  = SDL_MapRGB(screen->format, 240, 230, 140);
	particleColors[PT_SALTSPOUT]  = SDL_MapRGB(screen->format, 238, 233, 233);
	particleColors[PT_OILSPOUT]   = SDL_MapRGB(screen->format, 104,  44,  44);

	// Floating
	particleColors[PT_STEAM] = SDL_MapRGB(screen->format,  95, 158, 160);
	particleColors[PT_FIRE]  = SDL_MapRGB(screen->format, 255,  50,  50);

	// Fluids
	particleColors[PT_WATER] = SDL_MapRGB(screen->format,  32,  32, 255);
	particleColors[PT_DIRT]  = SDL_MapRGB(screen->format, 205, 175, 149);
	particleColors[PT_SALT]  = SDL_MapRGB(screen->format, 255, 255, 255);
	particleColors[PT_OIL]   = SDL_MapRGB(screen->format, 128,  64,  64);
	particleColors[PT_SAND]  = SDL_MapRGB(screen->format, 238, 204, 128);

	// Combined
	particleColors[PT_SALTWATER] = SDL_MapRGB(screen->format,  65, 105, 225);
	particleColors[PT_MUD]       = SDL_MapRGB(screen->format, 139,  69,  19);
	particleColors[PT_ACID]      = SDL_MapRGB(screen->format, 173, 255,  47);

	// Special
	particleColors[PT_ELECTRIC] = SDL_MapRGB(screen->format, 255, 255,   0);
}

/**
 * Frees the particle canvas.
 */
void deinitParticles(void) {
	free(particleScreen);
}

/**
 * Particle update routine which should be called every tick. Loops through each
 * particle on the screen and applies logic to it.
 *
 * @see doParticleLogic()
 */
void updateParticles(void) {
	int x, y;

	for (y = 0; y < PARTICLECANVAS_HEIGHT; y++) {
		// Randomly choose horizontal direction (LTR or RTL)
		if (ONEIN(2)) {
			for (x = 0; x < PARTICLECANVAS_WIDTH; x++)
				doParticleLogic(x, y);
		} else {
			for (x = PARTICLECANVAS_WIDTH; x--;) {
				doParticleLogic(x, y);
			}
		}
	}

	// Clear particles on the first and last rows of the screen to prevent
	// segfaults. Messy, but this is how SDL Sand works.
	for (x = 0; x < PARTICLECANVAS_WIDTH; x++) {
		PARTICLE(x, 0) = PT_NONE;
		PARTICLE(x, PARTICLECANVAS_HEIGHT-1) = PT_NONE;
	}
}

/**
 * Function which loops through each visible particle and draws them.
 */
void drawParticles(void) {
	if (SDL_MUSTLOCK(screen))
		if (SDL_LockSurface(screen) < 0)
			return;

	int x, y;
	for (y = PARTICLECANVAS_HEIGHT; y--;) {
		for (x = PARTICLECANVAS_WIDTH; x--;) {
			if (PARTICLE(x, y) == PT_NONE)
				continue;

			// Change PT_XMOVED to PT_X for non-stillborn particles
			if (!isStillborn(PARTICLE(x, y)) && isMovedParticle(PARTICLE(x, y)))
				PARTICLE(x, y)--;

			drawParticle16(x, y, PARTICLE(x, y));
		}
	}

	if (SDL_MUSTLOCK(screen))
		SDL_UnlockSurface(screen);
}

/**
 * Function to add particles to the canvas through mouse or joystick
 * (touchscreen) input. Particles are drawn in a circular fashion, with the
 * center being the point of click/touch.
 *
 * @param xpos horizontal position on the canvas
 * @param ypos vertical position on the canvas
 * @param radius circle radius
 */
void addParticles(int xpos, int ypos, int radius) {
	int x, y;
	for(x = ((xpos - radius - 1) < 0) ? 0 : (xpos - radius - 1); x <= (xpos + radius) && (x < PARTICLECANVAS_WIDTH); x++)
		for(y = ((ypos - radius - 1) < 0) ? 0 : (ypos - radius - 1); y <= (ypos + radius) && (y < PARTICLECANVAS_HEIGHT); y++)
			if ((SQR(x - xpos) + SQR(y - ypos)) <= SQR(radius))
				PARTICLE(x, y) = currentParticleType;
}

/**
 * Completely erases the particle canvas.
 */
void clearParticleScreen(void) {
	int x, y;
	for (x = 0; x < PARTICLECANVAS_WIDTH; x++)
		for (y = 0; y < PARTICLECANVAS_HEIGHT; y++)
			PARTICLE(x, y) = PT_NONE;
}

// Static functions
/**
 * Function to check whether a particle is stillborn.
 *
 * @param pt particle type
 */
static inline bool isStillborn(particleType_t pt) {
	return ((pt >= PT_STILLBORN_LOWER_BOUND) && (pt <= PT_STILLBORN_UPPER_BOUND));
}

/**
 * Function to check whether a particle is floating upwards (aka negative
 * gravity).
 *
 * @param pt particle type
 */
static inline bool isFloating(particleType_t pt) {
	return ((pt >= PT_FLOATING_LOWER_BOUND) && (pt <= PT_FLOATING_UPPER_BOUND));
}

/**
 * Function to check whether a particle is burnable.
 *
 * @param pt particle type
 */
static inline bool isBurnable(particleType_t pt) {
	return (
		(pt == PT_PLANT)
		|| (pt == PT_OIL) || (pt == PT_MOVEDOIL)
	);
}

/**
 * Function to check whether a praticle burns as ember. Ember particles change
 * into PT_EMBER upon igniting, instead of PT_FIRE. This makes them burn more
 * slowly than fire.
 *
 * @param pt particle type
 */
static inline bool burnsAsEmber(particleType_t pt) {
	return (pt == PT_PLANT);
}

/**
 * Function to check whether a particle has moved this tick. Bit messy solution
 * which was copied from SDL Sand.
 *
 * @param pt particle type
 */
static inline bool isMovedParticle(particleType_t pt) {
	return ((pt % 2) == 1);
}

/**
 * Function which plots a particle on the screen.
 *
 * @param x horizontal particle position
 * @param y vertical particle position
 * @param pt particle type
 */
static inline void drawParticle16(int x, int y, particleType_t pt) {
#if (PARTICLE_ZOOM == 1)
	*((Uint16 *)screen->pixels + (((y * screen->pitch) / 2) + x)) = particleColors[pt];
#else
	SDL_Rect dst;
	dst.x = x * PARTICLE_ZOOM;
	dst.y = y * PARTICLE_ZOOM;
	dst.w = PARTICLE_ZOOM;
	dst.h = PARTICLE_ZOOM;

	SDL_FillRect(screen, &dst, particleColors[pt]);
#endif
}

/**
 * Main function which handles all the particle logic. This function is called
 * each frame by each particle.
 *
 * -todo: add clarification how particles interact with which particles-
 *
 * @param x horizontal particle position
 * @param y vertical particle position
 */
static void doParticleLogic(int x, int y) {
	if (PARTICLE(x, y) == PT_NONE)
		return;

	if (isStillborn(PARTICLE(x, y))) {
		switch (PARTICLE(x, y)) {

		// PT_WALL

		// Rust slowly spreads sidewards and downards on iron walls
		case PT_IRONWALL:
			if (ONEIN(200) && ((PARTICLE_ABOVE(x, y) == PT_RUST) || (PARTICLE_LEFT(x, y) == PT_RUST) || (PARTICLE_RIGHT(x, y) == PT_RUST)))
				PARTICLE(x, y) = PT_RUST;
			break;

		// Torches occasionally spawn fire and always evaporate touching water
		// and saltwater
		case PT_TORCH:
			if (ONEIN(2)) {
				if (PARTICLE_ABOVE(x, y) == PT_NONE) PARTICLE_ABOVE(x, y) = PT_MOVEDFIRE;
				if (PARTICLE_LEFT(x, y)  == PT_NONE) PARTICLE_LEFT(x, y)  = PT_MOVEDFIRE;
				if (PARTICLE_RIGHT(x, y) == PT_NONE) PARTICLE_RIGHT(x, y) = PT_MOVEDFIRE;
			}

			if ((PARTICLE_ABOVE(x, y) == PT_WATER) || (PARTICLE_ABOVE(x, y) == PT_MOVEDWATER)) PARTICLE_ABOVE(x, y) = PT_MOVEDSTEAM;
			if ((PARTICLE_LEFT(x, y)  == PT_WATER) || (PARTICLE_LEFT(x, y)  == PT_MOVEDWATER)) PARTICLE_LEFT(x, y)  = PT_MOVEDSTEAM;
			if ((PARTICLE_BELOW(x, y) == PT_WATER) || (PARTICLE_BELOW(x, y) == PT_MOVEDWATER)) PARTICLE_BELOW(x, y) = PT_MOVEDSTEAM;
			break;

		// Stoves occasionally boil water into steam and saltwater into salt +
		// steam. Also burns oil.
		case PT_STOVE:
			// Boil water
			if (ONEIN(4) && (PARTICLE_ABOVE(x, y) == PT_WATER)) PARTICLE_ABOVE(x, y) = PT_STEAM;

			// Boil saltwater
			if (ONEIN(4) && (PARTICLE_ABOVE(x, y) == PT_SALTWATER)) {
				PARTICLE_ABOVE(x, y) = PT_SALT;
				PARTICLE_ABOVETWO(x, y) = PT_STEAM;
			}

			// Ignite oil
			if (ONEIN(8) && (PARTICLE_ABOVE(x, y) == PT_OIL)) PARTICLE_ABOVE(x, y) = PT_EMBER;
			break;

		// PT_ICE

		// Rust very slowly deteriates
		case PT_RUST:
			if (ONEIN(7000)) PARTICLE(x, y) = PT_NONE;
			break;

		// Ember is like fire, although stillborn and burns out slowly. Spreads
		// fire into plants.
		case PT_EMBER:
			if ((PARTICLE_BELOW(x, y) == PT_NONE) || isBurnable(PARTICLE_BELOW(x, y)))
				PARTICLE_BELOW(x, y) = PT_FIRE;

				switch (rand() % 4) {
				case 0: if (PARTICLE_ABOVE(x, y) == PT_PLANT) PARTICLE_ABOVE(x, y) = PT_FIRE; break;
				case 1: if (PARTICLE_BELOW(x, y) == PT_PLANT) PARTICLE_BELOW(x, y) = PT_FIRE; break;
				case 2: if (PARTICLE_LEFT(x, y)  == PT_PLANT) PARTICLE_LEFT(x, y)  = PT_FIRE; break;
				case 3: if (PARTICLE_RIGHT(x, y) == PT_PLANT) PARTICLE_RIGHT(x, y) = PT_FIRE; break;
				}

				// Burn out slowly
				if (ONEIN(18))
					PARTICLE(x, y) = PT_NONE;

			break;

		// Plants occasionally expand in non-moved water in a random direction,
		// consuming the water in the process
		case PT_PLANT:
			if (ONEIN(2)) {
				switch (rand() % 4) {
				case 0: if (PARTICLE_ABOVE(x, y) == PT_WATER) PARTICLE_ABOVE(x, y) = PT_PLANT; break;
				case 1: if (PARTICLE_BELOW(x, y) == PT_WATER) PARTICLE_BELOW(x, y) = PT_PLANT; break;
				case 2: if (PARTICLE_LEFT(x, y)  == PT_WATER) PARTICLE_LEFT(x, y)  = PT_PLANT; break;
				case 3: if (PARTICLE_RIGHT(x, y) == PT_WATER) PARTICLE_RIGHT(x, y) = PT_PLANT; break;
				}
			}
			break;

		case PT_VOID: // Voids eliminate all touching particles
			if ((PARTICLE_ABOVE(x, y) != PT_NONE) && (PARTICLE_ABOVE(x, y) != PT_VOID)) PARTICLE_ABOVE(x, y) = PT_NONE;
			if ((PARTICLE_BELOW(x, y) != PT_NONE) && (PARTICLE_BELOW(x, y) != PT_VOID)) PARTICLE_BELOW(x, y) = PT_NONE;
			if ((PARTICLE_LEFT(x, y) != PT_NONE) &&  (PARTICLE_LEFT(x, y)  != PT_VOID)) PARTICLE_LEFT(x, y)  = PT_NONE;
			if ((PARTICLE_RIGHT(x, y) != PT_NONE) && (PARTICLE_RIGHT(x, y) != PT_VOID)) PARTICLE_RIGHT(x, y) = PT_NONE;
			break;

		// Waterspout occasionally spawns water below
		case PT_WATERSPOUT:
			if (ONEIN(6)) {
				if (PARTICLE_BELOW(x, y) == PT_NONE) PARTICLE_BELOW(x, y) = PT_MOVEDWATER;
			}
			break;

		// Sandspout occasionally spawns sand below
		case PT_SANDSPOUT:
			if (ONEIN(6)) {
				if (PARTICLE_BELOW(x, y) == PT_NONE) PARTICLE_BELOW(x, y) = PT_MOVEDSAND;
			}
			break;

		// Saltspout occasionally spawns salt below
		case PT_SALTSPOUT:
			if (ONEIN(6)) {
				if (PARTICLE_BELOW(x, y) == PT_NONE) PARTICLE_BELOW(x, y) = PT_MOVEDSALT;
			}
			break;

		// Gee, I wonder what this one does... Probably spawns oil below!
		case PT_OILSPOUT:
			if (ONEIN(6)) {
				if (PARTICLE_BELOW(x, y) == PT_NONE) PARTICLE_BELOW(x, y) = PT_MOVEDOIL;
			}
			break;

		default:
			break;
		}

	} else {
		// Fluid particles (ie. non-stillborn) update 1/2nd of a time. Update
		// only non-moved particles.
		if (ONEIN(2) && !isMovedParticle(PARTICLE(x, y))) {
			// Set to moved
			PARTICLE(x, y)++;

			// Handle gravity
			if (!isFloating(PARTICLE(x, y))) {
				if ((rand() % 8) && (PARTICLE_BELOW(x, y) == PT_NONE)) {
					PARTICLE_BELOW(x, y) = PARTICLE(x, y);
					PARTICLE(x, y) = PT_NONE;
					return;
				}
			} else {
				if (ONEIN(3))
					return;

				if (
					(rand() % 8)
					&& ((PARTICLE_ABOVE(x, y) == PT_NONE) || (PARTICLE_ABOVE(x, y) == PT_FIRE))
					&& (PARTICLE(x, y) != PT_ELECTRIC)
					&& (PARTICLE(x, y) != PT_MOVEDELECTRIC)
				) {
					// Fire burns out eventually
					if (ONEIN(20) && (PARTICLE(x, y) == PT_MOVEDFIRE)) {
						PARTICLE(x, y) = PT_NONE;

					// Float up
					} else {
						PARTICLE_ABOVE(x, y) = PARTICLE(x, y);
						PARTICLE(x, y) = PT_NONE;
					}

					return;
				}
			}

			// Used for picking a random direction (up, down, left right) on
			// certain particles to perform action against.
			particleType_t *pickedParticle = NULL;

			// Particle logic
			switch (PARTICLE(x, y)) {

			// Steam has a chance to die out and a smaller chance to return to
			// water while floating.
			case PT_MOVEDSTEAM:
				if (ONEIN(1000)) PARTICLE(x, y) = PT_MOVEDWATER;
				if (ONEIN(500))  PARTICLE(x, y) = PT_NONE;

				// If steam comes in contact with a falling particle, it has a
				// 1/15 chance to die out and 14/15 chance to swap places with
				// the falling particle.
				if (!isStillborn(PARTICLE_ABOVE(x, y)) && !isFloating(PARTICLE_ABOVE(x, y))) {
					if (ONEIN(15)) {
						PARTICLE(x, y) = PT_NONE;
						return;
					} else {
						PARTICLE(x, y) = PARTICLE_ABOVE(x, y);
						PARTICLE_ABOVE(x, y) = PT_MOVEDSTEAM;
						return;
					}
				}
				break;

			// Fire dies out fast if in contact with non-burnable stuff. Makes
			// ice melt into water and burns everything else.
			// Burn, hehe. BUUURRNNNN!!!
			case PT_MOVEDFIRE:
				// Die out
				if (ONEIN(10) && !isBurnable(PARTICLE_ABOVE(x, y))) {
					PARTICLE(x, y) = PT_NONE;
					return;
				}

				// Melt ice
				if (ONEIN(4)) {
					if (PARTICLE_ABOVE(x, y) == PT_ICE) { PARTICLE_ABOVE(x, y) = PT_WATER; PARTICLE(x, y) = PT_NONE; }
					if (PARTICLE_BELOW(x, y) == PT_ICE) { PARTICLE_BELOW(x, y) = PT_WATER; PARTICLE(x, y) = PT_NONE; }
					if (PARTICLE_LEFT(x, y)  == PT_ICE) { PARTICLE_LEFT(x, y)  = PT_WATER; PARTICLE(x, y) = PT_NONE; }
					if (PARTICLE_RIGHT(x, y) == PT_ICE) { PARTICLE_RIGHT(x, y) = PT_WATER; PARTICLE(x, y) = PT_NONE; }
				}

				// Burn everything else
				switch (rand() % 4) {
				case 0: pickedParticle = &PARTICLE_ABOVE(x, y); break;
				case 1: pickedParticle = &PARTICLE_BELOW(x, y); break;
				case 2: pickedParticle = &PARTICLE_LEFT(x, y); break;
				case 3: pickedParticle = &PARTICLE_RIGHT(x, y); break;
				}

				if (isBurnable(*pickedParticle)) {
					if (burnsAsEmber(*pickedParticle)) {
						*pickedParticle = PT_EMBER;
					} else {
						*pickedParticle = PT_FIRE;
					}
				}
				break;

			// Water causes iron walls below to slowly rust, evaporates upon
			// contact with fire, merges with dirt into mud, merges with salt
			// into saltwater and sometimes melts ice. Is there anything water
			// cannot do?
			case PT_MOVEDWATER:
				// Cause ironwall below to rust
				if (ONEIN(200) && (PARTICLE_BELOW(x, y) == PT_IRONWALL))
					PARTICLE_BELOW(x, y) = PT_RUST;

				// Evaporate upon contact with fire
				if (
					(PARTICLE_ABOVE(x, y) == PT_FIRE)
					|| (PARTICLE_BELOW(x, y) == PT_FIRE)
					|| (PARTICLE_LEFT(x, y)  == PT_FIRE)
					|| (PARTICLE_RIGHT(x, y) == PT_FIRE)
				)
					PARTICLE(x, y) = PT_MOVEDSTEAM;

				// Merge with dirt into mud
				if (PARTICLE_ABOVE(x, y) == PT_DIRT) {
					PARTICLE_ABOVE(x, y) = PT_MOVEDMUD;
					PARTICLE(x, y) = PT_NONE;
				}

				if (PARTICLE_BELOW(x, y) == PT_DIRT) {
					PARTICLE_BELOW(x, y) = PT_MOVEDMUD;
					PARTICLE(x, y) = PT_NONE;
				}

				// Merge with salt into saltwater
				if (PARTICLE_ABOVE(x, y) == PT_SALT) {
					PARTICLE_ABOVE(x, y) = PT_MOVEDSALTWATER;
					PARTICLE(x, y) = PT_NONE;
				}

				if (PARTICLE_BELOW(x, y) == PT_SALT) {
					PARTICLE_BELOW(x, y) = PT_MOVEDSALTWATER;
					PARTICLE(x, y) = PT_NONE;
				}

				// Melt ice
				if (ONEIN(60)) {
					switch (rand() % 4) {
					case 0: pickedParticle = &PARTICLE_ABOVE(x, y); break;
					case 1: pickedParticle = &PARTICLE_BELOW(x, y); break;
					case 2: pickedParticle = &PARTICLE_LEFT(x, y); break;
					case 3: pickedParticle = &PARTICLE_RIGHT(x, y); break;
					}

					if (*pickedParticle == PT_ICE)
						*pickedParticle = PT_WATER;
				}
				break;

			// PT_MOVEDDIRT

			// Salt sometimes melts ice
			case PT_MOVEDSALT:
				if (ONEIN(20)) {
					switch (rand() % 4) {
					case 0: pickedParticle = &PARTICLE_ABOVE(x, y); break;
					case 1: pickedParticle = &PARTICLE_BELOW(x, y); break;
					case 2: pickedParticle = &PARTICLE_LEFT(x, y); break;
					case 3: pickedParticle = &PARTICLE_RIGHT(x, y); break;
					}

					if (*pickedParticle == PT_ICE)
						*pickedParticle = PT_WATER;
				}
				break;

			// Oil is HIGHLY flammable and combusts when in contact with fire,
			// ember and stoves, burning everything around it.
			case PT_MOVEDOIL:
				if (
					(PARTICLE_ABOVE(x, y) == PT_FIRE) || (PARTICLE_BELOW(x, y) == PT_FIRE) || (PARTICLE_LEFT(x, y) == PT_FIRE) || (PARTICLE_RIGHT(x, y) == PT_FIRE)
					|| (PARTICLE_ABOVE(x, y) == PT_EMBER) || (PARTICLE_BELOW(x, y) == PT_EMBER) || (PARTICLE_LEFT(x, y)  == PT_EMBER) || (PARTICLE_RIGHT(x, y) == PT_EMBER)
					|| (PARTICLE_ABOVE(x, y) == PT_STOVE) || (PARTICLE_BELOW(x, y) == PT_STOVE) || (PARTICLE_LEFT(x, y)  == PT_STOVE) || (PARTICLE_RIGHT(x, y) == PT_STOVE)
				) {
					PARTICLE(x, y) = PT_FIRE;
					PARTICLE_ABOVE(x, y) = PT_FIRE;
					PARTICLE_BELOW(x, y) = PT_FIRE;
					PARTICLE_LEFT(x, y) = PT_FIRE;
					PARTICLE_RIGHT(x, y) = PT_FIRE;
				}
				break;

			// PT_MOVEDSAND

			// Saltwater melts ice more slowly than pure salt
			case PT_MOVEDSALTWATER:
				if (ONEIN(40)) {
					switch (rand() % 4) {
					case 0: pickedParticle = &PARTICLE_ABOVE(x, y); break;
					case 1: pickedParticle = &PARTICLE_BELOW(x, y); break;
					case 2: pickedParticle = &PARTICLE_LEFT(x, y); break;
					case 3: pickedParticle = &PARTICLE_RIGHT(x, y); break;
					}

					if (*pickedParticle == PT_ICE)
						*pickedParticle = PT_WATER;
				}
				break;

			// PT_MOVEDMUD

			// Acid burns through everything but walls, ironwalls and water
			case PT_MOVEDACID:
				switch (rand() % 4) {
				case 0: pickedParticle = &PARTICLE_ABOVE(x, y); break;
				case 1: pickedParticle = &PARTICLE_BELOW(x, y); break;
				case 2: pickedParticle = &PARTICLE_LEFT(x, y); break;
				case 3: pickedParticle = &PARTICLE_RIGHT(x, y); break;
				}

				if (
					(*pickedParticle != PT_WALL)
					&& (*pickedParticle != PT_IRONWALL)
					&& (*pickedParticle != PT_WATER)
					&& (*pickedParticle != PT_MOVEDWATER)
					&& (*pickedParticle != PT_ACID)
					&& (*pickedParticle != PT_MOVEDACID)
				)
					*pickedParticle = PT_NONE;
				break;

			// Electric dies out fast. Not really sure what this is for.
			case PT_MOVEDELECTRIC:
				if (ONEIN(2))
					PARTICLE(x, y) = PT_NONE;
				break;


			default:
				break;
			}

			// 'Particle swaps'. Makes oil float on water, dirt/mud sink in
			// water, etc.
			switch(PARTICLE(x, y)) {
			// Water floats on sand, saltwater and mud.
			case PT_MOVEDWATER:
				if (
					ONEIN(3)
					&& (
						(PARTICLE_ABOVE(x, y) == PT_SAND)
						|| (PARTICLE_ABOVE(x, y) == PT_SALTWATER)
						|| (PARTICLE_ABOVE(x, y) == PT_MUD)
					)
				) {
					PARTICLE(x, y) = PARTICLE_ABOVE(x, y);
					PARTICLE_ABOVE(x, y) = PT_MOVEDWATER;
					return;
				}
				break;

			// Oil floats on top of water. Poor birds..
			case PT_MOVEDOIL:
				if (
					ONEIN(3)
					&& (PARTICLE_ABOVE(x, y) == PT_WATER)
				) {
					PARTICLE(x, y) = PARTICLE_ABOVE(x, y);
					PARTICLE_ABOVE(x, y) = PT_MOVEDOIL;
					return;
				}
				break;

			// Saltwater floats on top of dirt, sand and mud.
			case PT_MOVEDSALTWATER:
				if (
					ONEIN(3)
					&& (
						(PARTICLE_ABOVE(x, y) == PT_DIRT)
						|| (PARTICLE_ABOVE(x, y) == PT_SAND)
						|| (PARTICLE_ABOVE(x, y) == PT_MUD)
					)
				) {
					PARTICLE(x, y) = PARTICLE_ABOVE(x, y);
					PARTICLE_ABOVE(x, y) = PT_MOVEDSALTWATER;
					return;
				}
				break;

			default:
				break;
			}

			// The following code makes particles slide and move randomly left
			// and right. Messy code from SDL Sand, but cba fixing it now.
			if (!isFloating(PARTICLE(x, y))) {
				particleType_t *firstParticle      = NULL;
				particleType_t *secondParticle     = NULL;
				particleType_t *firstParticleDown  = NULL;
				particleType_t *secondParticleDown = NULL;

				if (ONEIN(2)) {
					firstParticle      = &PARTICLE_LEFT(x, y);
					secondParticle     = &PARTICLE_RIGHT(x, y);
					firstParticleDown  = &PARTICLE_LEFT(x, y+1);
					secondParticleDown = &PARTICLE_RIGHT(x, y+1);
				} else {
					firstParticle      = &PARTICLE_RIGHT(x, y);
					secondParticle     = &PARTICLE_LEFT(x, y);
					firstParticleDown  = &PARTICLE_RIGHT(x, y+1);
					secondParticleDown = &PARTICLE_LEFT(x, y+1);
				}

				// Handle diagonal falling first...
				if (*firstParticleDown == PT_NONE) {
					*firstParticleDown = PARTICLE(x, y);
					PARTICLE(x, y) = PT_NONE;
				} else if (*secondParticleDown == PT_NONE) {
					*secondParticleDown = PARTICLE(x, y);
					PARTICLE(x, y) = PT_NONE;

				// ...before horizontal sliding
				} else if (*firstParticle == PT_NONE) {
					*firstParticle = PARTICLE(x, y);
					PARTICLE(x, y) = PT_NONE;
				} else if (*secondParticle == PT_NONE) {
					*secondParticle = PARTICLE(x, y);
					PARTICLE(x, y) = PT_NONE;
				}

			// Same story with steam, although upwards
			} else if (PARTICLE(x, y) == PT_MOVEDSTEAM) {
				particleType_t *firstParticle    = NULL;
				particleType_t *secondParticle   = NULL;
				particleType_t *firstParticleUp  = NULL;
				particleType_t *secondParticleUp = NULL;

				if (ONEIN(2)) {
					firstParticle    = &PARTICLE_LEFT(x, y);
					secondParticle   = &PARTICLE_RIGHT(x, y);
					firstParticleUp  = &PARTICLE_LEFT(x, y-1);
					secondParticleUp = &PARTICLE_RIGHT(x, y-1);
				} else {
					firstParticle    = &PARTICLE_RIGHT(x, y);
					secondParticle   = &PARTICLE_LEFT(x, y);
					firstParticleUp  = &PARTICLE_RIGHT(x, y-1);
					secondParticleUp = &PARTICLE_LEFT(x, y-1);
				}

				// Handle diagonal floating first...
				if (*firstParticleUp == PT_NONE) {
					*firstParticleUp = PARTICLE(x, y);
					PARTICLE(x, y) = PT_NONE;
				} else if (*secondParticleUp == PT_NONE) {
					*secondParticleUp = PARTICLE(x, y);
					PARTICLE(x, y) = PT_NONE;

				// ...before horizontal sliding
				} else if (*firstParticle == PT_NONE) {
					*firstParticle = PARTICLE(x, y);
					PARTICLE(x, y) = PT_NONE;
				} else if (*secondParticle == PT_NONE) {
					*secondParticle = PARTICLE(x, y);
					PARTICLE(x, y) = PT_NONE;
				}
			}
		}
	}
}
